using System;
using UnityEngine;

namespace DanielSig.Geometry
{
	public struct Orientation32 : IOrientation, I32Bit
	{
		#region type casts
		public static explicit operator Orientation32(Coordinates64 original)
		{
			return new Orientation32(original);
		}
		public static explicit operator Orientation32(Coordinates32 original)
		{
			return new Orientation32(original);
		}
		public static explicit operator Orientation32(Vector3 vector)
		{
			return new Orientation32(vector);
		}
		public static explicit operator Orientation32(Quaternion quaternion)
		{
			return quaternion * new Orientation32(0, 0, 1);
		}
		public static implicit operator Vector3(Orientation32 coordinates)
		{
			return coordinates.vector;
		}
		public static implicit operator Quaternion(Orientation32 coordinates)
		{
			return Quaternion.LookRotation(coordinates.vector);
		}
		#endregion
		#region operators
		public static Orientation32 operator *(Quaternion quaternion, Orientation32 orientation)
		{
			return new Orientation32(quaternion * orientation.vector);
		}
		public static Orientation32 operator *(Orientation32 orientation, Quaternion quaternion)
		{
			return new Orientation32(quaternion * orientation.vector);
		}
		public static Orientation32 operator +(Orientation32 ori1, Orientation32 ori2)
		{
			return Orientation32.Euler(SphereMath.FixAngles(ori1.eulerAngles + ori2.eulerAngles));
		}
		public static Orientation32 operator -(Orientation32 ori1, Orientation32 ori2)
		{
			return Orientation32.Euler(SphereMath.FixAngles(ori1.eulerAngles - ori2.eulerAngles));
		}
		public static Orientation32 operator +(Orientation32 orientation, Vector3 vector)
		{
			return new Orientation32(orientation.vector + vector);
		}
		public static Orientation32 operator -(Orientation32 orientation, Vector3 vector)
		{
			return new Orientation32(orientation.vector - vector);
		}
		#endregion
		#region constructors
		internal Orientation32(uint data)
		{
			_data = data;
		}
		public Orientation32(Vector3 vector)
		{
			_data = 0;
			this.vector = vector;
		}
		public Orientation32(Orientation32 original)
		{
			_data = original._data;
		}
		public Orientation32(Coordinates32 original)
		{
			_data = (original.rawData >> Z_ANGLE_BITS) << Z_ANGLE_BITS;
		}
		public Orientation32(Coordinates64 original)
		{
			_data = (((Coordinates32)original).rawData >> Z_ANGLE_BITS) << Z_ANGLE_BITS;
		}
		public Orientation32(float x, float y, float z)
				 : this(new Vector3(x, y, z))
		{
		}
		#endregion
		
		
		public static Orientation32 Euler(Vector3 vector)
		{
			return (Orientation32)Quaternion.Euler(vector);
		}
		public static Orientation32 Euler(float x, float y, float z)
		{
			return (Orientation32)Quaternion.Euler(x, y, z);
		}
		public override string ToString ()
		{
			return string.Format ("[Orientation32: vector={0}, \nxAngle={1}, yAngle={2}, zAngle={3}]", vector, xAngle, yAngle, zAngle);
		}
		#region getters
		#region angle getters
		public Vector3 eulerAngles
		{
			get
			{
				return new Vector3(xAngle, yAngle, zAngle);
			}
			set
			{
				_data = 0;
				_data += (uint)Mathf.Round(Mathf.Repeat(value.x / 180F, 1F) * X_ANGLE_RESOLUTION) << (Z_ANGLE_BITS + Y_ANGLE_BITS);
				_data += (uint)Mathf.Round(Mathf.Repeat(value.y / 360F, 1F) * Y_ANGLE_RESOLUTION) << Z_ANGLE_BITS;
				_data += (uint)Mathf.Round(Mathf.Repeat(value.z / 360F, 1F) * Z_ANGLE_RESOLUTION);
			}
		}
		public Vector2 angles
		{
			get
			{
				return new Vector2(xAngle, yAngle);
			}
			set
			{
				_data = (_data << (X_ANGLE_BITS + Y_ANGLE_BITS)) >> (X_ANGLE_BITS + Y_ANGLE_BITS);
				_data += (uint)Mathf.Round(Mathf.Repeat(value.x / 180F, 1F) * X_ANGLE_RESOLUTION) << (Z_ANGLE_BITS + Y_ANGLE_BITS);
				_data += (uint)Mathf.Round(Mathf.Repeat(value.y / 360F, 1F) * Y_ANGLE_RESOLUTION) << Z_ANGLE_BITS;
			}
		}
		#endregion
		#region indevidual angle getters
		public float xAngle
		{
			get
			{
				return ((_data >> (Y_ANGLE_BITS + Z_ANGLE_BITS)) / X_ANGLE_RESOLUTION) * 180F;
			}
			set
			{
				_data = (_data << X_ANGLE_BITS) >> X_ANGLE_BITS;
				_data += (uint)Mathf.Round(Mathf.Repeat(value / 180F, 1F) * X_ANGLE_RESOLUTION) << (Z_ANGLE_BITS + Y_ANGLE_BITS);
			}
		}
		public float yAngle
		{
			get
			{
				return (((_data << X_ANGLE_BITS) >> (X_ANGLE_BITS + Z_ANGLE_BITS)) / Y_ANGLE_RESOLUTION) * 360F;
			}
			set
			{
				_data -= (_data << X_ANGLE_BITS) >> (X_ANGLE_BITS + Z_ANGLE_BITS);
				_data += (uint)Mathf.Round(Mathf.Repeat(value / 360F, 1F) * Y_ANGLE_RESOLUTION) << Z_ANGLE_BITS;
			}
		}
		public float zAngle
		{
			get
			{
				return (_data - ((_data >> Z_ANGLE_BITS) << Z_ANGLE_BITS)) / Z_ANGLE_RESOLUTION;
			}
			set
			{
				_data = (_data >> Z_ANGLE_BITS) << Z_ANGLE_BITS;
				_data += (uint)Mathf.Round(Mathf.Repeat(value / 360F, 1F) * Z_ANGLE_RESOLUTION);
			}
		}
		public uint rawData
		{
			get
			{
				return _data;
			}
			set
			{
				_data = value;
			}
		}
		#endregion
		#region vector getters
		public Vector3 vector
		{
			get
			{
				return SphereMath.ToVector(xAngle, yAngle, 1);
			}
			set
			{
				eulerAngles = SphereMath.ToAngles(value);
			}
		}
		public float x
		{
			get
			{
				return SphereMath.GetVectorX(xAngle, yAngle, 1);
			}
			set
			{
				eulerAngles = SphereMath.ToAngles(value, y, z);
			}
		}
		public float y
		{
			get
			{
				return SphereMath.GetVectorY(xAngle, yAngle, 1);
			}
			set
			{
				eulerAngles = SphereMath.ToAngles(x, value, z);
			}
		}
		public float z
		{
			get
			{
				return SphereMath.GetVectorZ(xAngle, yAngle, 1);
			}
			set
			{
				eulerAngles = SphereMath.ToAngles(x, y, value);
			}
		}
		#endregion
		#endregion
		private uint _data;
		
		private const int X_ANGLE_BITS = 10;
		private const int Y_ANGLE_BITS = 11;
		private const int Z_ANGLE_BITS = 11;
		
		private const float X_ANGLE_RESOLUTION = (1 << X_ANGLE_BITS) - 1;
		private const float Y_ANGLE_RESOLUTION = (1 << Y_ANGLE_BITS) - 1;
		private const float Z_ANGLE_RESOLUTION = (1 << Z_ANGLE_BITS) - 1;
	}
}